/*	
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { AstTreeUtils, ArkFile, ts } from "arkanalyzer";
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Rule, Defects, FileMatcher, MatcherTypes, MatcherCallback } from '../../Index';
import { RuleListUtil } from "../../utils/common/DefectsList";
import { IssueReport } from "../../model/Defects";
let optionIncludeExports = false;
interface RuleOption {
    includeExports?: boolean;
}
const gMetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: 'docs/no-duplicate-imports-check.md',
    description: '"module" import is duplicated.'
};
let filePath = '';
export class NoDuplicateImportsCheck implements BaseChecker {
    private importedModules: Map<string, ts.Node[]> = new Map();
    private namespaceImportsExports: Map<string, ts.Node[]> = new Map();
    private directImports: Map<string, ts.Node[]> = new Map();
    private typeImports: Map<string, ts.Node[]> = new Map();
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private buildMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };
    public registerMatchers(): MatcherCallback[] {
        const matchBuildCb: MatcherCallback = {
            matcher: this.buildMatcher,
            callback: this.check.bind(this)
        };
        return [matchBuildCb];
    };
    public check = (arkFile: ArkFile) => {
        filePath = arkFile.getFilePath();
        const ruleOption = this.rule.option?.[0] as RuleOption | undefined;
        optionIncludeExports = ruleOption?.includeExports ?? false;
        this.importedModules.clear();
        this.namespaceImportsExports.clear();
        this.typeImports.clear();
        const astRoot = AstTreeUtils.getASTNode(arkFile.getName(), arkFile.getCode());
        for (let child of astRoot.statements) {
            this.checkImportDeclaration(child);
        };
    };
    private checkImportDeclaration = (node: ts.Node): void => {
        if (ts.isImportDeclaration(node)) {
            const isTypeOnly = node.importClause?.isTypeOnly ?? false;
            if (!node.importClause?.namedBindings) {
                const moduleSpecifier = node.moduleSpecifier;
                if (!moduleSpecifier || !ts.isStringLiteral(moduleSpecifier)) {
                    return;
                }
                const moduleName = isTypeOnly ? moduleSpecifier.text : `'${moduleSpecifier.text}'`;
                const nodes = this.directImports.get(moduleName) || [];
                const inportNodes = this.importedModules.get(moduleName) || [];
                const aliasNodes = this.directImports.get(moduleName) || [];
                const errorMessage = `${moduleName} ${isTypeOnly ? 'type import is duplicated.' : 'import is duplicated.'}`;
                if (aliasNodes.length > 0 || inportNodes.length > 0) {
                    this.addIssueReport(node, errorMessage, this.metaData.severity);
                    return;
                };
                this.directImports.set(moduleName, [...nodes, node]);
                return;
            };
            this.processModuleSpecifier(node, true, isTypeOnly);
        } else if (optionIncludeExports && ts.isExportDeclaration(node)) {
            this.processModuleSpecifier(node, false, false);
        };
        ts.forEachChild(node, this.checkImportDeclaration);
    };
    private processModuleSpecifier = (node: ts.ImportDeclaration | ts.ExportDeclaration, isImport: boolean, typeOnly: boolean): void => {
        const moduleSpecifier = node.moduleSpecifier;
        if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier)) {
            let moduleName = moduleSpecifier.text;
            const action = typeOnly ? ' type' : '';
            const importOrExport = isImport ? 'import' : 'export';
            const asimport = isImport ? '' : ' as import';
            moduleName = typeOnly ? `${moduleName}` : `'${moduleName}'`;
            const errorMessage = moduleName + `${action} ${importOrExport} is duplicated${asimport}.`;
            if (typeOnly) {
                if (this.handleTypeImportExportStatement(node, moduleName, errorMessage)) {
                    return;
                };
            };
            this.handleImportExportStatement(node, moduleName, errorMessage, isImport, typeOnly);
        };
    };
    private handleTypeImportExportStatement(node: ts.ImportDeclaration | ts.ExportDeclaration, moduleName: string, errorMessage: string): boolean {
        const nodes = this.typeImports.get(moduleName) || [];
        if (nodes.length > 0) {
            this.addIssueReport(node, errorMessage, this.metaData.severity);
            return true;
        };
        this.typeImports.set(moduleName, [...nodes, node]);
        return false;
    };
    private handleImportExportStatement(node: ts.ImportDeclaration | ts.ExportDeclaration, moduleName: string,
        errorMessage: string, isImport: boolean, typeOnly: boolean): void {
        const aliasNodes = this.directImports.get(moduleName) || [];
        if (!typeOnly) {
            if (aliasNodes.length > 0) {
                this.addIssueReport(node, errorMessage, this.metaData.severity);
                return;
            };
        };
        const alias = this.getNamespaceAlias(node);
        const key = `${moduleName}|${alias || ''}`;
        if (alias) {
            if (aliasNodes.length > 0) {
                this.addIssueReport(node, errorMessage, this.metaData.severity);
                return;
            };
            this.checkAndStore(isImport ? node as ts.ImportDeclaration : node as ts.ExportDeclaration, key, this.namespaceImportsExports, errorMessage);
        } else {
            this.checkAndStore(isImport ? node as ts.ImportDeclaration : node as ts.ExportDeclaration, moduleName, this.importedModules, errorMessage);
        };
    };
    private checkAndStore(node: ts.Node, key: string, storage: Map<string, ts.Node[]>, errorMessage: string): void {
        if (storage.has(key)) {
            const previousNodes = storage.get(key)!;
            if (this.shouldReportImportExport(node, previousNodes)) {
                this.addIssueReport(node, errorMessage, this.metaData.severity);
            };
        };
        storage.set(key, [...storage.get(key) || [], node]);
    };
    private shouldReportImportExport(node: ts.Node, previousNodes: ts.Node[]): boolean {
        return previousNodes.some(prevNode => {
            if (ts.isImportDeclaration(prevNode) && ts.isImportDeclaration(node)) {
                return true;
            };
            if (optionIncludeExports && ts.isExportDeclaration(prevNode) && ts.isExportDeclaration(node)) {
                return true;
            };
            if (optionIncludeExports && ts.isImportDeclaration(prevNode) && ts.isExportDeclaration(node)) {
                return true;
            };
            if (optionIncludeExports && ts.isExportDeclaration(prevNode) && ts.isImportDeclaration(node)) {
                return true;
            }
            return false;
        });
    };
    private getNamespaceAlias(node: ts.ImportDeclaration | ts.ExportDeclaration): string | undefined {
        if (ts.isImportDeclaration(node)) {
            const namedBindings = node.importClause?.namedBindings;
            if (namedBindings && ts.isNamespaceImport(namedBindings)) {
                return namedBindings.name.text;
            };
        } else if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamespaceExport(node.exportClause)) {
            return node.exportClause.name.text;
        };
        return undefined;
    };
    private addIssueReport(node: ts.Node, description: string, severity: number): void {
        const warnInfo = this.getLineAndColumn(node);
        const defect = new Defects(warnInfo.line, warnInfo.startCol, warnInfo.endCol, description, severity,
            this.rule.ruleId, warnInfo.filePath, this.metaData.ruleDocPath, true, false, false, false);
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    };
    private getLineAndColumn(node: ts.Node): { line: number; startCol: number; endCol: number; filePath: string } {
        const sourceFile = node.getSourceFile();
        const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
        const endCharacter = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).character;
        return {
            line: line + 1,
            startCol: character + 1,
            endCol: endCharacter + 1,
            filePath: filePath
        };
    };
};
